There isn't, by default, a 'publish' link because of the way WordPress handles publishing posts. 'publish' is not consider an action in the same way 'delete' is, instead you save the post, with all its data, including its post status.
To get round this you can create your own action handler. In order to avoid potential clashes with WP or other plug-ins, you should probably prefix your custom action, or at the very least your nonce.
The following code should work for this link (note prefixed action and nonce):
$url = add_query_arg(array('action'=>'mypublish', 'post'=>$post->ID),home_url());
echo "<a href='" . wp_nonce_url($url, 'my-publish-post_' . $post->ID) . "'>Publish</a>";
The following function performs some checks, publishes the post and then re-directs you to the published post.
//Run the publish function only if the action variable is correct.
if(isset($_REQUEST['action']) && $_REQUEST['action']=='mypublish')
//Get the post's ID.
$post_id = (isset($_REQUEST['post']) ? (int) $_REQUEST['post'] : 0);
//No post? Oh well..
$nonce = $_REQUEST['_wpnonce'];
if (! wp_verify_nonce($nonce,'my-publish-post_'.$post_id))
wp_die('Are you sure?');
//Check user permissions
wp_die("You can't do that");
//Any other checks you may wish to perform..
wp_publish_post( $post_id );
//Redirect to published post
$redirect = get_permalink($post_id);
As mentioned in the comments, slugs are not set when using this method. WordPress does not automatically give draft posts a slug, but only when you click 'Publish'. The above does not update the slug, just the status. To get round this you can manually set the slug when you create the draft.
Alternatively, you can replace the
wp_publish_post call with the following (slightly heavier and a bit uglier) code:
$post = get_post($post_id,ARRAY_A);